function varargout = create_category(varargin)

%  __  __                            _   _       
% |  \/  |                          | | | |      
% | \  / | __ _ _ __ __ _ _   _  ___| |_| |_ ___ 
% | |\/| |/ _` | '__/ _` | | | |/ _ \ __| __/ _ \
% | |  | | (_| | | | (_| | |_| |  __/ |_| ||  __/
% |_|  |_|\__,_|_|  \__, |\__,_|\___|\__|\__\___|
%                      | |                       
%                      |_|                       
%  _    _       _                    _ _         
% | |  | |     (_)                  (_) |        
% | |  | |_ __  ___   _____ _ __ ___ _| |_ _   _ 
% | |  | | '_ \| \ \ / / _ \ '__/ __| | __| | | |
% | |__| | | | | |\ V /  __/ |  \__ \ | |_| |_| |
%  \____/|_| |_|_| \_/ \___|_|  |___/_|\__|\__, |
%                                           __/ |
%                                          |___/ 
%                                          
% Last Modified by Dexter Delfrate for 
% Marquette University's Speech and Signal Processing Lab.
%
% This file is part of the Recognition Toolkit.
%
% To run this program, open Run_Me.m and select 'Create Category'.
% 
% This module is for creating, editing, and viewing the contents of
%      categories. If there are any categories previously defined, these will be 
%      loaded on start and appear in the lower box.
%      A new category can be created by clicking the "Add..." button. The category
%      will be added to the lower box (provided it does not violate naming 
%      conventions). Double-clicking on the name of a category will open it for 
%      viewing/editing (note that directly editing the contents is not 
%      recommended, changes should not be saved). 
%      Use the "Browse..." button to select a directory containing data files (the 
%      default directory is the current working directory). Double-clicking the
%      ".." will navigate up one level, and double-clicking on the name of a 
%      folder will open that folder.
%      To apply categories to data files, select the desired data files from the 
%      upper box and the desired categories from the lower box, then click the 
%      "Apply Categories" button. A dialog will confirm completion.
% 
%      NOTE: Deleting a category will remove it from the list but will not 
%      physically remove the file containing its data. To recover a deleted 
%      category, just create a new one with the same name.
%  
%
%   See also ERRORDLG, FEOF, FGETL, FOPEN, FPRINTF, FREWIND, ISNUMERIC, OPEN,
%       QUESTDLG, STRCMP, STRFIND, STRREP, UIGETDIR, WAITBAR.
%
%   Copyright 2009 Marquette University, Speech and Signal Processing
%   Laboratory.


% Begin initialization code - DO NOT EDIT
gui_Singleton = 1;
gui_State = struct('gui_Name',       mfilename, ...
                   'gui_Singleton',  gui_Singleton, ...
                   'gui_OpeningFcn', @create_category_OpeningFcn, ...
                   'gui_OutputFcn',  @create_category_OutputFcn, ...
                   'gui_LayoutFcn',  [] , ...
                   'gui_Callback',   []);
if nargin && ischar(varargin{1})
    gui_State.gui_Callback = str2func(varargin{1});
end

if nargout
    [varargout{1:nargout}] = gui_mainfcn(gui_State, varargin{:});
else
    gui_mainfcn(gui_State, varargin{:});
end

% End initialization code - DO NOT EDIT


% --- Executes just before create_category is made visible.

%% Opening function and initialization

function create_category_OpeningFcn(hObject, eventdata, handles, varargin) %#ok<*INUSL>
% This function has no output args, see OutputFcn.
% hObject    handle to figure
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)
% varargin   command line arguments to create_category (see VARARGIN)
% Choose default command line output for create_category
handles.output = hObject;
global main_dir; 
if isnumeric(main_dir), main_dir = strrep(which('create_category_OpeningFcn'),'\create_category.m','');end
% Update handles structure
guidata(hObject, handles);
setappdata(hObject, 'StartPath', pwd);
addpath(pwd);
initial_dir = pwd;
load_listbox(initial_dir,handles);
if ~exist([main_dir '\Data\Categories'],'dir')
    mkdir([main_dir 'Data\Categories']);
end
all_cats={''};
cats_path = [main_dir '\Data\Categories\Master_Cat.category'];
handles.cats_path = cats_path;
%Checks to see if a master categories file already exists and if so, loads
%   all categories into the category box. This is accomplished by reading
%   each line from the file, making it into a cell string and sticking the
%   line into the next cell of the array. The array is then sorted into
%   alphabetical order and passed to the load_catsbox function.
if exist(cats_path,'file')
   catfile = fopen(cats_path, 'r'); flag =0;
   while ~feof(catfile)
       try
           if flag ==0
               all_cats(end) = strrep(cellstr(fgetl(catfile)),'"','');
               flag =1;
           else
               all_cats(end+1) = strrep(cellstr(fgetl(catfile)),'"',''); %#ok<*AGROW>
           end
       catch e
           disp(e)
           if strcmp(all_cats(end),'')
               all_cats(end) = [];
           end
       end
   end
   fclose(catfile);
   all_cats = sortrows(all_cats);
   load_catsbox(all_cats,handles);   
end
handles.all_cats = all_cats;
%The next line is what allows us to use the handles.var structure. If this line
%   appears in the main OpeningFcn, hObject would be acceptable for the
%   first input argument. Otherwise, you need to explicitly point it at the
%   handle of the GUI as shown here.
guidata(handles.createcategory,handles)
load_listbox(initial_dir,handles);
set(gcf,'CloseRequestFcn',@my_closefcn);

%% Load category box

function load_catsbox(all_cats, handles)
if ~isempty(all_cats)
    set(handles.categories,'string',all_cats);
    set(handles.apply_button,'enable','on');
    set(handles.delete_button,'enable','on');
end


%% Close function

function my_closefcn(hObject, eventdata, handles, varargin) %#ok<*INUSD>
%Before closing, make sure there are no remaining files left open, and
%   return to our main_dir.
global main_dir;
if ~isempty(fopen('all'))
    fclose('all');
end
cd(main_dir);
delete(gcf);
htk_tools %comment this line out for convenience while bug testing.

% UIWAIT makes create_category wait for user response (see UIRESUME)
% uiwait(handles.createcategory);

%% Load file list box

function load_listbox(dir_path,handles)
cd (dir_path)
dir_struct = dir(dir_path);
bad_files = [];
for i=1:length(dir_struct)
    if ~dir_struct(i).isdir
        [path,name,ext] = fileparts(dir_struct(i).name);
        if ~strcmpi(ext,'.wav')
            bad_files(end+1) = i;
        end
    end   
end
dir_struct(bad_files) = [];
[sorted_names,sorted_index] = sortrows({dir_struct.name}');
handles.file_names = sorted_names;
handles.is_dir = [dir_struct.isdir];
handles.sorted_index = sorted_index;

set(handles.data_box,'String',handles.file_names,...
	'Value',1)
set(handles.text2,'String',pwd)
set(handles.text2,'tooltipstring',pwd)
guidata(handles.createcategory, handles);

% --- Outputs from this function are returned to the command line.
%% varargout

function varargout = create_category_OutputFcn(hObject, eventdata, handles) 
% varargout  cell array for returning output args (see VARARGOUT);
% hObject    handle to figure
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)
% Get default command line output from handles structure
varargout{1} = handles.output;


% --- Executes on selection change in data_box.
%% Data box callback

function data_box_Callback(hObject, eventdata, handles)
% hObject    handle to data_box (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)
% Hints: contents = get(hObject,'String') returns data_box contents as cell array
%        contents{get(hObject,'Value')} returns selected item from data_box
get(handles.createcategory,'SelectionType');
%If a folder, '.', or '..' is double-clicked in the list, either open that
%   folder, refresh the list box, or navigate up one level (respectively).
if strcmp(get(handles.createcategory,'SelectionType'),'open')
    index_selected = get(handles.data_box,'Value');
    file_list = get(handles.data_box,'String');
    filename = file_list{index_selected};
    if  handles.is_dir(handles.sorted_index(index_selected))
        cd (filename)
        load_listbox(pwd,handles)
    end
end

% --- Executes during object creation, after setting all properties.
%% Data box creation

function data_box_CreateFcn(hObject, eventdata, handles)
% hObject    handle to data_box (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    empty - handles not created until after all CreateFcns called
% Hint: listbox controls usually have a white background on Windows.
%       See ISPC and COMPUTER.
if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor','white');
end
tip = ['<HTML>Select data files you wish to associate with the below selected categories.<br>',...
    'Double-clicking the "." will refresh the contents of this box.<br>You may also navigate up or down the',...
    ' directory levels by double-clicking<br> either the ".." or the name of a folder (respectively).'];
set(hObject, 'tooltipstring', tip);
set(hObject, 'max', 2);

% --- Executes on button press in browse_button.
%% Browse button

function browse_button_Callback(hObject, eventdata, handles)
% hObject    handle to browse_button (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)
path = uigetdir;
if path ~= 0
load_listbox(path,handles)
end

% --- Executes on selection change in categories.
%% Categories box callback

function categories_Callback(hObject, eventdata, handles)
%On double click, makes sure only one category is selected and opens that
%   category file for the user to inspect or edit its contents. If there
%   are no data files associated with the selected category (.category file
%   does not exist yet) an error will be displayed. A warning dialog will
%   appear explaining the risks of editing the file directly. The file will
%   not open until the user has responded to the warning dialog.
global main_dir;
if strcmp(get(handles.createcategory,'SelectionType'),'open') & size(get(hObject,'value')) == 1 %#ok<AND2>
    selected = get(hObject,'value');
    cats = get(hObject,'string');
    filename = [main_dir, '\Data\Categories\', cats{selected}, '.category'];
    if ~exist(filename,'file')
        errordlg('There are no files associated with that category yet.','Whoops!');
    else
        h= questdlg(sprintf(['Editing this file could damage the program''s ability to function properly. Saving changes to the file is not reccommended.\n',...
            'Note that merely viewing the file will not cause damage, but may cause an error if it is still open when you try to assign files to this category.\n'...
            'Are you sure you would like to open this file?']),'WARNING!','View the file','Cancel','Cancel');
        switch h
            case 'View the file'
                open(filename);
            case 'Cancel'
                return;
        end
    end
    
end


% --- Executes during object creation, after setting all properties.
%% Categories box creation

function categories_CreateFcn(hObject, eventdata, handles)
% hObject    handle to categories (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    empty - handles not created until after all CreateFcns called
% Hint: listbox controls usually have a white background on Windows.
%       See ISPC and COMPUTER.
if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor','white');
end

% --- Executes on button press in delete_button.
%% Delete category button

function delete_button_Callback(hObject, eventdata, handles)
% hObject    handle to delete_button (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)
for_real = questdlg(sprintf(['Are you sure you want to delete the selected categories?\n'...
    'NOTE: Deleting a category will not physically remove the file containing its data.']),...
    'Delete Category',...
    'Yes','No','No');
switch for_real
    case 'Yes'
        selection = get(handles.categories,'value');
        cats = get(handles.categories,'string');
        cats(selection) = [];
        handles.all_cats = cats;
        %Updates the Master category file to reflect the deletion of the selected
        %   categories.
        catfile = fopen(handles.cats_path, 'w+');
        for x=1:length(cats)
            fprintf(catfile,[cats{x}, '\n']);
        end
        fclose(catfile);
        %If all categories have been deleted, disable the Delete and Apply buttons.
        if isempty(cats)
            set(handles.delete_button,'enable','off');
            set(handles.apply_button,'enable','off');
            
        end
        selection =1;
%         selection = min(selection,size(cats));
        set(handles.categories, 'value',selection, 'string', cats);
    case 'No'
        return
end


% --- Executes on button press in add_button.
%% Add category button

function add_button_Callback(hObject, eventdata, handles)
% hObject    handle to add_button (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)
    cats = get(handles.categories,'string');
    new = strtrim(inputdlg('Name of category to create','Create Category'));
    if isempty(new), return, end
    if strcmpi(new, 'Master_Cat')
        errordlg('This is a reserved category name. Please choose another name','Whoops!');
        return;
    end
%Prevent invalid filename characters    
    if check_invalid(new, handles) ~= 0 
        return;
    end
%Makes sure category does not already exist.    
    already_exists =0;
    for i=1:length(cats) 
       if strcmpi(cats{i},new) 
           already_exists=1; break;
       end
    end
    if ~isempty(new) && ~(strcmp(new,'')) && already_exists==0
        if isempty(cats)
            cats = new;
        else
            cats(end+1) = new;
        end
        cats = sortrows(cats);
        handles.all_cats = cats;
        set(handles.categories,'string',cats)
        set(handles.categories, 'value', 1)
        set(handles.delete_button,'enable', 'on');
        set(handles.apply_button,'enable','on');
%Updates the Master category file.        
        catfile = fopen(handles.cats_path, 'w+');
        for x=1:length(cats)
            fprintf(catfile,['"' cats{x} '"' '\n']);
        end
        fclose(catfile);
    end


%% Invalid filename checking

function uberflag = check_invalid(new_cat, handles)
%This function checks the user's input to make sure there are no invalid
%   filename characters used. And yes, I really did call it an uberflag.
invalids = {'\', '/', '?', '%', '#', '.', '$', '(', ')', '&', '@', '`', '~','{', '}', '[', ']', '+', '=', '-', '''', ' ', '^','!', ',', '*', ':', ';', '|', '"', '<', '>'};
uberflag = 0;
for x=1:length(invalids)
    h=strfind(new_cat, invalids{x});
    if ~isempty(h{1})
       uberflag = 1; break
    end
end
if uberflag ==1
    if strcmp(invalids{x},' '), invalids{x} = '[space]'; end
    errordlg(['Sorry, but ' invalids{x} ' is an invalid character. Please try a different name.'],'Whoops!');
end
    
% --- Executes on button press in apply_button.
%% Apply category button

function apply_button_Callback(hObject, eventdata, handles) %#ok<*DEFNU>
global main_dir;
cats = get(handles.categories,'string');
vals_c = get(handles.categories,'value');
vals_f = get(handles.data_box,'value');
data = get(handles.data_box,'string');
selection_c = cats(vals_c);
selection_f = data(vals_f);
goback = pwd;
from_where = [get(handles.text2,'string') '/'];

%The String Replace function strrep() is important here because fprintf() won't
%   work if you have single forward slashes in your string. This is because
%   it interprets the \ as the start of an escape character. Replacing \
%   with / causes everything to output smoothly.
from_where = strrep(from_where,'\','/');
num_tasks = length(selection_c)*length(selection_f);
h = waitbar(0,'This may take a moment...','name','Applying categories...');
progress = 0;

cd([main_dir '\Data\Categories']);

%Create (or append to) a .category file for each selected category, and
%   fill it with the full file paths (enclosed in double quotes) of all files 
%   belonging to the selected categories. Will not allow a file to appear more
%   than once within a category. If the user has selected a folder, '.' or '..'
%   from the data list box, these values will not be written to file.
for c=1:length(selection_c)
    if ~exist([selection_c{c},'.category'],'file')
        outputfile = fopen([selection_c{c},'.category'],'w+'); 
    else
    outputfile = fopen([selection_c{c},'.category'],'r+'); 
    end
    warning off MATLAB:strrep:InvalidInputType
    %Read each line of the file into the files cell, stripping the double
    %   quotes at first to ease the duplication checking process. The MATLAB
    %   warning is temporarily turned off here to avoid the warning thrown if
    %   the file is empty.
    files{1} = strrep(fgetl(outputfile),'"','');
    while ~feof(outputfile)
        files{end+1} = strrep(fgetl(outputfile), '"','');
    end
    warning on MATLAB:strrep:InvalidInputType

    if files{1} == -1
        files(1) = [];
    end
    for f=1:length(selection_f)
        flag = 0;
        %Checks to make sure that folder names, '.' and '..' will not be 
        %   written to the file, setting 'skip item' flag if needed.
        if  handles.is_dir(handles.sorted_index(vals_f(f))) | strcmp(selection_f{f},'.') ...
                    | strcmp(selection_f{f},'..') %#ok<OR2>
            flag = 1; 
        end      
        % Checks if the current item already exists in the file, and sets the
        %   'skip item' flag to true if necessary.
        k = strfind(files,[from_where, selection_f{f}]);
        for r =1:length(k)
            if ~isempty(k{r})
                flag =1; break;
            end
        end


        %If the 'skip item' flag has not been set, add the current item to
        %   the files cell and sort all entries of the cell alphabetically.
        %   Rewind to the beginning of the file and write in each item,
        %   putting back the double-quotes.
        if flag==0
            files{end+1} = [from_where, selection_f{f}];
            files = sort(files);
            frewind(outputfile);          
            for i = 1:length(files)
                
                fprintf(outputfile, ['"' files{i} '"'  '\n']);
            end
        end 
        progress = progress+1;
        waitbar(progress/num_tasks,h);
    end
    fclose(outputfile);
end
if exist('h','var')
    delete(h);
end
fclose('all');
msgbox('Files categorized!');
cd(goback);

%% Menu items

function file_menu_Callback(hObject, eventdata, handles)
% hObject    handle to file_menu (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)


function help_menu_Callback(hObject, eventdata, handles)
% hObject    handle to help_menu (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)


function exit_Callback(hObject, eventdata, handles)
% hObject    handle to exit (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)
my_closefcn(hObject,eventdata,handles)


function about_us_Callback(hObject, eventdata, handles)
% hObject    handle to about_us (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)
about_us;


function help_Callback(hObject, eventdata, handles)
% hObject    handle to help (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)
help_launch('cats');
%% Help callback
